/*
	Copyright (C) 2011 Simas Toleikis

	This file is part of Die2Nite Agent.

	Die2Nite Agent is free software: you can redistribute it and/or modify
	it under the terms of the GNU General Public License as published by
	the Free Software Foundation, either version 3 of the License, or
	(at your option) any later version.

	This program is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
	GNU General Public License for more details.

	You should have received a copy of the GNU General Public License
	along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

"use strict";

// External map applications
var mapApplications = {

	atlas: {
		name: "Atlas",
		id: 12,
		successMatch: /^1$/,
		updateURL: "http://atlas.wonderfulfailure.com/scripts/updater.php",
		keySelector: 'form[action*="atlas.wonderfulfailure.com"] input[name=key]'
	},
	ovalOffice: {
		name: "Oval Office",
		id: 10,
		successMatch: /map has been updated/i,
		updateURL: "http://d2n.sindevel.com/oo/upd.php",
		keySelector: 'form[action*="d2n.sindevel.com/oo"] input[name=key]'
	}
};

// Initialize options
var defaultOptions = {
	compactMode: false,
	expertMode: false,
	waitTime: 3
};

for (var applicationID in mapApplications) {
	defaultOptions["update-" + applicationID] = true;
}

var options = new Store("settings", defaultOptions);

// Default AJAX setup
$.ajaxSetup({cache: false, timeout: 30000});

// Get application secure key from disclaimer page
function getSecureKey(applicationID, port) {

	mapApplications[applicationID].status = "calibrating";

	var ajaxRequest = {};

	ajaxRequest.type = "GET";
	ajaxRequest.url = "http://www.die2nite.com/disclaimer";
	ajaxRequest.data = { id: mapApplications[applicationID].id };

	ajaxRequest.complete = (function(appID) { return function(xhr, textStatus) {

		var requestFailed = true;

		if (textStatus == "success") {

			var key = $(xhr.responseText).find(mapApplications[appID].keySelector).val();

			if (key) {
				localStorage.setItem("key-" + appID, key);
				requestFailed = false;
			}
		}

		if (requestFailed) {
			mapApplications[appID].status = "nokey";
		}
		else {
			updateMap(appID, port);
		}

		if (port) {
			updateMapStatus(port);
		}

	}})(applicationID);

	$.ajax(ajaxRequest);
}

// Update map status icon and text
function updateMapStatus(port, status) {

	var message = null;

	if (!status) {

		status = "updated";
		var isUpdating = false;
		var isFailed = false;
		var hasKeys = true;

		message = '<div id="d2na-map-status-text">';
		var messageStatusList = "<ul>";

		for (var applicationID in mapApplications) {

			var application = mapApplications[applicationID];

			if (application.status && options.get("update-" + applicationID)) {

				var isBroken = application.status == "failed" || application.status == "nokey";

				messageStatusList += "<li" + (isBroken ? ' class="broken"' : "") + ">";
				messageStatusList += '<img src="' + chrome.extension.getURL("img/icon-" + applicationID + ".png") + '" /> ';
				messageStatusList += application.name + ": <strong>"

				switch (application.status) {

					case "updating": {
						messageStatusList += "Updating...";
						isUpdating = true;
						break;
					}
					case "calibrating": {
						messageStatusList += "Calibrating...";
						isUpdating = true;
						break;
					}
					case "waiting": {
						messageStatusList += "Waiting...";
						isUpdating = true;
						break;
					}
					case "updated": {
						messageStatusList += "Updated!";
						break;
					}
					case "failed": {
						messageStatusList += "Update failed!";
						isFailed = true;
						break;
					}
					case "nokey": {
						messageStatusList += "No key!";
						isFailed = true;
						hasKeys = false;
						break;
					}
				}

				messageStatusList += "</strong></li>";
			}
		}

		messageStatusList += "</ul>";

		if (hasKeys) {
			message += "Current zone is synchronized with external map applications:";
		}
		else {
			message += "Make sure you have <strong>Authorise external applications</strong> checkbox checked on your settings page!";
		}

		message += "<br />" + messageStatusList + "</div>"

		// Some applications are still updating
		if (isUpdating) {
			status = "updating";
		}
		// Application failed with update
		else if (isFailed) {
			status = "failed";
		}
	}

	port.postMessage({type: "setMapStatus", status: status, message: message});
}

// Update external map
function updateMap(applicationID, port, waitTime) {

	mapApplications[applicationID].status = waitTime ? "waiting" : "updating";

	// AJAX request is scheduled (but not yet sent)
	if (mapApplications[applicationID].timeout) {
		clearTimeout(mapApplications[applicationID].timeout);
		mapApplications[applicationID].timeout = null;
	}

	var applicationKey = localStorage.getItem("key-" + applicationID);
	var ajaxRequest = {};

	ajaxRequest.type = "POST";
	ajaxRequest.data = { key: applicationKey };
	ajaxRequest.url = mapApplications[applicationID].updateURL;

	ajaxRequest.complete = (function(appID) { return function(xhr, textStatus) {

		if (typeof xhr.responseText != "string" || !xhr.responseText.match(mapApplications[appID].successMatch)) {
			textStatus = "error";
		}

		if (textStatus == "success") {
			mapApplications[appID].status = "updated";
			localStorage.setItem("lastUpdateFailed" + appID, 0);
		}
		else {
			mapApplications[appID].status = "failed";
			localStorage.setItem("lastUpdateFailed" + appID, 1);
		}

		updateMapStatus(port);

	}})(applicationID);

	// Register timed future call
	if (waitTime) {

		mapApplications[applicationID].timeout = setTimeout((function(req, appID) { return function() {

			clearTimeout(mapApplications[appID].timeout);

			mapApplications[appID].timeout = null;
			mapApplications[appID].status = "updating";

			updateMapStatus(port);

			$.ajax(req);

		}})(ajaxRequest, applicationID), waitTime);
	}
	else {
		$.ajax(ajaxRequest);
	}
}

// Compare given zone state to previous one
function checkZoneState(newZoneState) {

	var zoneStateString = localStorage.getItem("zoneState");

	if (!zoneStateString) {
		return false;
	}

	var zoneState = JSON.parse(zoneStateString);
	if (!zoneState) {
		return false;
	}

	// Compare states
	var isStateEqual = true;
	for (var prop in newZoneState) {

		if (zoneState[prop] === undefined || zoneState[prop] !== newZoneState[prop]) {
			isStateEqual = false;
			break;
		}
	}

	return isStateEqual;
}

// Handle messages from content script (long-lived connections)
chrome.extension.onConnect.addListener(function(port) {

	port.onMessage.addListener(function(msg) {

		if (msg.type == "updateMap") {

			var updateEnabled = false;
			var isZoneChanged = !checkZoneState(msg.zoneState);

			for (var applicationID in mapApplications) {

				// Skip disabled applications
				if (!options.get("update-" + applicationID)) {
					mapApplications[applicationID].status = null;
					continue;
				}

				updateEnabled = true;
				var lastUpdateFailed = (localStorage.getItem("lastUpdateFailed" + applicationID) == 1);

				// Calibrate (get secure key)
				if (!localStorage.getItem("key-" + applicationID) || lastUpdateFailed) {
					getSecureKey(applicationID, port);
					continue;
				}

				if (!isZoneChanged && !lastUpdateFailed && mapApplications[applicationID].status) {
					continue;
				}

				updateMap(applicationID, port, options.get("waitTime") * 1000);
			}

			// Update disabled (all external map applications are disabled in options)
			if (!updateEnabled) {
				updateMapStatus(port, "disabled");
			}
			else {
				localStorage.setItem("zoneState", JSON.stringify(msg.zoneState));
				updateMapStatus(port);
			}
		}
	});
});

// Handle messages from content script (simple connections)
chrome.extension.onRequest.addListener(function(msg, sender, sendResponse) {

	// Check content mode
	if (msg.type == "getContentMode") {

		sendResponse({
			type: msg.type,
			compactMode: options.get("compactMode"),
			expertMode: options.get("expertMode")
		});
	}
	else {
		sendResponse({});
	}
});